/** * PropertyAccessor - Helper class which accesses value object properties * * Copyright (c) 2002 * Marty Phelan, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package com.taursys.model; import java.text.Format; import java.text.MessageFormat; import com.taursys.util.DataTypes; import com.taursys.util.UnsupportedDataTypeException; import com.taursys.util.UnsupportedConversionException; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.text.ParseException; import com.taursys.debug.Debug; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import java.lang.IllegalAccessException; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.beans.IntrospectionException; import java.util.StringTokenizer; /** * This is a helper class which provides access to value object properties. * This class is constructed by with the class of the target value object * and the name of the target property within the value object. As part of * construction, introspection sets up the methods for reading and writing the * property. * <p> * This class provides two primary methods, getPropertyValue and setPropertyValue * which provide access to the property in the given value object. The * valueObject passed to these methods must be the same class that was used to * create this property accessor. * <p> * The readMethods or writeMethod may be null if the valueObject does * not have a cooresponding get or set method. This method uses reflection * and draws information from the valueObject's BeanInfo and PropertyDescriptors. * <p> * The property name should follow JavaBean naming conventions. If the * property you want to access is in a nested class, then separate the * property names by periods. * <p> * Example: propertyName="color": then it expects getColor and setColor * will be the method names in the valueObject. * <p> * Example: propertyName="address.city": Assumes your value object contains * an address property which in turn contains a city property. * Accessing the property will result in a call: getAddress().getCity() or * getAddress().setCity(...) */ public class PropertyAccessor { private String propertyName; private int javaDataType = DataTypes.TYPE_UNDEFINED; private java.lang.reflect.Method writeMethod; private java.lang.reflect.Method[] readMethods; private Class valueObjectClass; private boolean primative; public PropertyAccessor(Class valueObjectClass, String propertyName) throws ModelException { this.valueObjectClass = valueObjectClass; this.propertyName = propertyName; try { setAccessorMethods(); } catch (ModelException ex) { Debug.error("Error accessing property. " + ex.getMessage(), ex); throw ex; } } // *********************************************************************** // * INITIALIZATION METHODS // *********************************************************************** /** * Sets the javaDataType, readMethods and writeMethod properties. * The valueObject and propertyName must be valid before invoking this * method. * The readMethods or writeMethod may be null if the valueObject does * not have a cooresponding get or set method. This method uses reflection * and draws information from the valueObject's BeanInfo and PropertyDescriptors. * @throws ModelException if valueObjectClass is null, propertyName is null/blank, * property not found in class, or an IntrospectionException occurs. */ protected void setAccessorMethods() throws ModelException { if (valueObjectClass == null) throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_TARGET_CLASS_IS_NULL, valueObjectClass, propertyName); if (propertyName == null || propertyName.length() == 0) throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_PROPERTY_NAME_MISSING, valueObjectClass, propertyName); StringTokenizer tokenizer = new StringTokenizer(propertyName, "."); int count = tokenizer.countTokens() ; readMethods = new Method[count]; PropertyDescriptor prop = null; Class targetClass = valueObjectClass; for (int i = 0; i < count ; i++) { String propName = tokenizer.nextToken(); prop = getProperty(targetClass, propName); readMethods[i] = prop.getReadMethod(); targetClass = prop.getPropertyType(); } primative = targetClass.isPrimitive(); javaDataType = DataTypes.getDataType(targetClass.getName()); writeMethod = prop.getWriteMethod(); } /** * Searches the given class for the given property name and returns the PropertyDescriptor. * @throws ModelPropertyAccessorException if property is not found, or if an * IntrospectionException occurs. */ protected PropertyDescriptor getProperty(Class c, String propName) throws ModelPropertyAccessorException { BeanInfo info = null; try { info = Introspector.getBeanInfo(c); } catch (IntrospectionException ex) { throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_INTROSPECTION_EXCEPTION, c, propName); } PropertyDescriptor[] props = info.getPropertyDescriptors(); for( int i=0 ; i < props.length ; i++ ) { if (props[i].getName().equals(propName)) { return props[i]; } } throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_PROPERTY_NOT_FOUND, c, propName); } // *********************************************************************** // * PROPERTY VALUE ACCESSOR METHODS // *********************************************************************** /** * Returns a property value from the valueObject based on the propertyName. * @return Object obtained from invocation of getter method * @throws ModelException if property has no read method defined or if an * IllegalAccessException or IllegalArgumentException occurs. * @throws ModelInvocationTargetException if the invoked read method throws an * Exception. */ public Object getPropertyValue(Object valueObject) throws ModelException { Object target = valueObject; try { for (int i = 0; target != null && i < readMethods.length; i++) { target = invokeReadMethod(i, target); } return target; } catch (ModelException ex) { Debug.error("Error accessing property. " + ex.getMessage(), ex); throw ex; } } /** * Sets a property of the valueObject to the given value based on the propertyName. * @throws ModelException if property has no write method defined or if an * IllegalAccessException or IllegalArgumentException occurs. * @throws ModelInvocationTargetException if the invoked read method throws an * Exception. */ public void setPropertyValue(Object valueObject, Object value) throws ModelException { Object target = valueObject; try { int count = readMethods.length -1; for (int i = 0; target != null && i < count ; i++) { target = invokeReadMethod(i, target); } invokeWriteMethod(target, value); } catch (ModelException ex) { // Debug if this is not NULL for primative exception if (!(ex instanceof ModelPropertyAccessorException && ((ModelPropertyAccessorException)ex).getReason() == ModelPropertyAccessorException.REASON_NULL_VALUE_FOR_PRIMATIVE)) { Debug.error("Error accessing property. " + ex.getMessage(), ex); } throw ex; } } /** * Invokes the i(th) read method on the given target and returns results. * @return Object obtained from invocation of getter method * @throws ModelException if property has no read method defined or if an * IllegalAccessException occurs. * @throws ModelInvocationTargetException if the invoked read method throws an * Exception. */ protected Object invokeReadMethod(int i, Object target) throws ModelException { if (target == null) return null; if (readMethods[i] == null) throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_NO_READ_METHOD_FOR_PROPERTY, target.getClass(), propertyName); try { return readMethods[i].invoke(target, new Object[] {}); } catch (InvocationTargetException ex) { throw new ModelInvocationTargetException( valueObjectClass, propertyName, javaDataType, target, readMethods[i], ex); } catch (IllegalAccessException ex) { throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_ILLEGAL_ACCESS_EXCEPTION, valueObjectClass, propertyName, javaDataType, target, readMethods[i], ex); } catch (IllegalArgumentException ex) { throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_ILLEGAL_ARGUMENT_EXCEPTION, valueObjectClass, propertyName, javaDataType, target, readMethods[i], ex); } } /** * Invokes the write method on the given target. * @throws ModelException if property has no write method defined or if an * IllegalAccessException or IllegalArgumentException occurs. * @throws ModelInvocationTargetException if the invoked read method throws an * Exception. */ protected void invokeWriteMethod(Object target, Object value) throws ModelException { if(target == null) throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_TARGET_IS_NULL, valueObjectClass, propertyName); if(writeMethod == null) throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_NO_WRITE_METHOD_FOR_PROPERTY, valueObjectClass, propertyName); if(value == null && isPrimative()) throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_NULL_VALUE_FOR_PRIMATIVE, valueObjectClass, propertyName, javaDataType, target, writeMethod); try { writeMethod.invoke(target, new Object[] {value}); } catch (InvocationTargetException ex) { throw new ModelInvocationTargetException( valueObjectClass, propertyName, javaDataType, target, value, writeMethod, ex); } catch (IllegalAccessException ex) { throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_ILLEGAL_ACCESS_EXCEPTION, valueObjectClass, propertyName, javaDataType, target, value, writeMethod, ex); } catch (IllegalArgumentException ex) { throw new ModelPropertyAccessorException( ModelPropertyAccessorException.REASON_ILLEGAL_ARGUMENT_EXCEPTION, valueObjectClass, propertyName, javaDataType, target, value, writeMethod, ex); } } // *********************************************************************** // * PROPERTY ACCESSOR METHODS // *********************************************************************** /** * Returns the JavaDataType of the property in the valueObject. */ public int getJavaDateType() { return javaDataType; } /** * Returns the name of the property to access in the valueObjectClass. * This is the property that get/setPropertyValue methods will access. * The property name should follow JavaBean naming conventions. If the * property you want to access is in a nested class, then separate the * property names by periods. * <p> * Example: propertyName="color": then it expects getColor and setColor * will be the method names in the valueObject. * <p> * Example: propertyName="address.city": Assumes your value object contains * an address property which in turn contains a city property. * Accessing the property will result in a call: getAddress().getCity() or * getAddress().setCity(...) */ public String getPropertyName() { return propertyName; } /** * Returns the Methods used to get/read the target property. * This value is set by setAccessorMethods which is invoked * automatically when this object is created. */ protected java.lang.reflect.Method[] getReadMethods() { return readMethods; } /** * Returns the Method used to get/read the target property. * This value is set by setAccessorMethods which is invoked * automatically when this object is created. */ protected java.lang.reflect.Method getWriteMethod() { return writeMethod; } /** * Returns the class of the ValueObject(or parent ValueObject) where the value resides. * This is the class that get/setPropertyValue methods will access. */ public Class getValueObjectClass() { return valueObjectClass; } /** * Indicates that the property is a primative data type. * @return true if the property is a primative data type. */ protected boolean isPrimative() { return primative; } }